會有這個需求是因為在前公司的時候,有個舊的 repo 執行的服務被因為裡頭的程式碼有太多舊的東西,但仍然有目前要用的部份,所以複製到了一個新的 repo。同時舊的這邊的改動又有需要更新到新的 repo,所以必須做到跨 repo 的 cherry-pick。查了一下,StackOverflow 上也有不少人問,打聽了一下身邊工程師的朋友們,也有人有過同樣的需求。這篇把我知道的 3 個方法整理起來,並列出哪個比較好及為什麼比較好的原因。
git format-patch -k --stdout ${commit_hash_1}..${commmit_hash_2} | git am -3 -k
這個應該是最好懂的,只要有在 GitHub 上 fork 過別人的 repo,並按照 GitHub 的官方教學把對方的 repo 用 git remote add
設定成 upstream 的人,對這套流程應該不陌生,大概就是以下的步驟:
git remote add
有你想要 cherry-pick
的 commit 的 repogit fetch
剛剛設定好的 remotegit checkout
到你要的 branch,然後用 git log
找尋你要的 commit,紀錄下來後就可以進行 cherry-pick
如果這樣還是不太懂的話,底下有人回覆了這篇文章:Git cherry-pick from another repository (Example),把完整的步驟與指令都清楚的列出來了。
這個方式很好懂,但很麻煩,因為步驟真的有點多。
比較進階一點的 Git user 會用這個方法,觀念上沒有很難懂,只是要瞭解 git diff
和 git apply
這兩個指令在幹嘛。其實從字面上看來大概就可以猜測到,git diff
生出一個 diff 檔,而 git apply
把這個 diff 檔 apply 到某個 branch 上。
git diff
應該對大部分的 git user 來說不陌生,主要都是在修改檔案後要 git add
之前,用 git diff ${FILE_PATH}
來確認某個檔案修改的部份。但 git diff
也可以用來看從 A commit 到 B commit 之間修改了哪些東西,用法也不難,git diff ${A_COMMIT_HASH} ${B_COMMIT_HASH}
就行了。
git apply
可能是一般 Git user 比較少用到的,但也沒有很難懂。man git-apply
顯示的簡介是 git-apply - Apply a patch to files and/or to the index,簡單來說就把一個 patch 檔 apply 到你現在的 branch。
知道了這兩個指令在幹嘛以後,我們就可以透過以下步驟來達到我們想要的效果:
git diff ${A_COMMIT_HASH} ${B_COMMIT_HASH} > xxx.patch
git apply xxx.patch
在我們要加入這些 commits 的 repo 使用這行指令把 commits 新增進來,以我的例子來說就是新 repo。
如果在 git apply
的過程中遇到 trailing whitespace error 的話,可以參考這篇文章:git - My diff contains trailing whitespace - how to get rid of it? - Stack Overflow,透過加入 --whitespace=warn
或 --whitespace=nowarn
參數來解決。
這樣是不是比上面那個步驟少多了?但這個方法有個缺點,就是使用 git apply
的話,committer 會是使用 git apply
的人,而不是原本的 committer,所以要介紹下面這個方法。
這個是 3 個方法裏面最推薦的,最後會講一下 GitHub 有一個小方法可以直接拿到 patch,可以直接給 git am
使用。
基本上這方法應該是最正統得如何把別人的 commit 拿來給自己用的方式了,GitHub 也只是把這個包裝起來而已。如果是已經習慣透過 email 或者論壇收發 patch 的 Open Source contributer 兼 git 使用者,應該對這個方法習以為常。
一般的 GitHub user 應該都對這兩個指令不熟,因為 GitHub 已經把這塊都處理好了,所以使用者基本上不太需要自己操作。man git-format-patch
和 man git-am
就可以看到,這兩個指令基本上都是設計成在 email 的環境下使用:
man git-format-patch
: git-format-patch - Prepare patches for e-mail submission
man git-am
: git-am - Apply a series of patches from a mailbox
有興趣的人可以自己去看個詳細,這邊就不多談,直接講使用方法:
git format-patch -k --stdout ${A_COMMIT_HASH}..${B_COMMIT_HASH} > xxx.patch
-k
,--keep-subject
: Do not strip/add [PATCH] from the first line of the commit log message.
-k
這個參數的話, commit log 的第一行就不會加上 [PATCH]
。git am -k -3 < xxx.patch
-k
是為了接收加了 -k
參數沒有加了 [PATCH]
字串的 patch 檔-3
是使用 three-way merge其實跟 git diff
+ git apply
非常像。
在 StackOverflow 上的這個回答 直接教你怎麼把這兩個指令合在 1 行解決。
git format-patch
的小功能GitHub 其實可以在 commit, pull request, compare 的網址後面加上 .patch
,就會拿到 git format-patch
產生的檔案,不過這邊的 output 是沒有加 -k
參數的,所以會有 "[PATCH]" 字串,以下是範例:
git format-patch
+ git am
" 比 "git diff
+ git apply
" 好A better way to exchange whole commits by file is the combination of the commands git format-patch on the sender and then git am on the receiver, because it also transfers the authorship info and the commit message.
如果覺得我的文章不錯的話,
請幫我按讚、追蹤、訂閱、留言、分享,
有任何問題也都歡迎留言討論,
也可以利用像是 Feedly 等 RSS Reader,
直接訂閱我的部落格:https://blog.m157q.tw。
iThome 這邊我應該只有鐵人賽的時候會使用。